/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.lang {

import asruby.Ruby;
import asruby.runtime.Block;
import asruby.runtime.CallbackFactory;
import asruby.runtime.IRubyObject;
import asruby.runtime.ObjectAllocator;
import asruby.runtime.ThreadContext;

/**
 * @author olabini
 */
public class RubyMatchData extends RubyObject {
    var regs:Region;        // captures
    var _begin:int;          // begin and end are used when not groups defined
    var _end:int;
    var str:RubyString;
    var pattern:Regex;
    
    public static function createMatchDataClass(runtime:Ruby):RubyClass{
        // TODO: Is NOT_ALLOCATABLE_ALLOCATOR ok here, since you can't actually instantiate MatchData directly?
        var matchDataClass: RubyClass =  runtime.defineClass("MatchData", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setMatchData(matchDataClass);
        runtime.defineGlobalConstant("MatchingData", matchDataClass);
        matchDataClass.kindOf = 
            function isKindOf(obj:IRubyObject, type:RubyModule):Boolean{
                return obj instanceof RubyMatchData;
            };
       
        var callbackFactory: CallbackFactory =  runtime.callbackFactory(RubyMatchData);

        matchDataClass.getMetaClass().undefineMethod("new");
        
        matchDataClass.defineAnnotatedMethods(RubyMatchData);
        matchDataClass.dispatcher = callbackFactory.createDispatcher(matchDataClass);

        return matchDataClass;
    }

    public  function RubyMatchData(runtime:Ruby){
        super(runtime, runtime.getMatchData());
    }

    public static var MATCH_BUSY: int =  USER2_F;

    // rb_match_busy
    public  function use0():void{
        flags |= MATCH_BUSY; 
    }

    public  function used():Boolean{
        return (flags & MATCH_BUSY) != 0;
    }

    private  function match_array(start:int):RubyArray{
        if (regs == null) {
            if (start != 0) return getRuntime().newEmptyArray();
            if (begin == -1) {
                return getRuntime().newArray(getRuntime().getNil());
            } else {
                var ss: RubyString =  str.makeShared(begin, end - begin);
                if (isTaint()) ss.setTaint(true);
                return getRuntime().newArray(ss);
            }
        } else {
            var arr: RubyArray =  getRuntime().newArray(regs.numRegs - start);
            for ( var int:i =start; i<regs.numRegs; i++) {
                if (regs.beg[i] == -1) {
                    arr.append(getRuntime().getNil());
                } else {
                    var ss: RubyString =  str.makeShared(regs.beg[i], regs.end[i] - regs.beg[i]);
                    if (isTaint()) ss.setTaint(true); 
                    arr.append(ss);
                }
            }
            return arr;
        }
        
    }

    public  function group(n:Number):IRubyObject{
        return RubyRegexp.nth_match(n, this);
    }

    public override function inspect():IRubyObject{
        return anyToString();
    }

    /** match_to_a
     *
     */
    public override function to_a():RubyArray{
        return match_array(0);
    }

    public  function values_at(args:Array):IRubyObject{
        return to_a().values_at(args);
    }

    public  function select(context:ThreadContext, block:Block):IRubyObject{
        return block.yield(context, to_a());
    }

    /** match_captures
     *
     */
    public  function captures():IRubyObject{
        return match_array(1);
    }

    private  function nameToBackrefNumber(str:RubyString):int{
        var value: ByteList =  str.getByteList();
        try {
            return pattern.nameToBackrefNumber(value.bytes, value.begin, value.begin + value.realSize, regs);
        } catch (je:JOniException) {
            throw getRuntime().newIndexError(je.getMessage());
        }
    }

    /** match_aref
     *
     */
    public  function op_aref(args:Array):IRubyObject{
        var rest: IRubyObject =  args.length == 2 ? args[1] : null;
        var idx: IRubyObject =  args[0];

        if (rest == null || rest.isNil()) {
            if (idx instanceof RubyFixnum) {
                var num: int =  RubyNumeric.fix2int(idx);
                if (num >= 0) return RubyRegexp.nth_match(num, this);                
            } else {
                var str:RubyString;
                if (idx instanceof RubySymbol) {
                    str = RubyString(idx).id2name();
                } else if (idx instanceof RubyString) {
                    str = RubyString(idx);
                } else {
                    return RubyArray(to_a()).aref(args);
                }
                return RubyRegexp.nth_match(nameToBackrefNumber(str), this);
            }
        }
        return RubyArray(to_a()).aref(args);
    }

    /** match_size
     *
     */
    public  function size():IRubyObject{
        return regs == null ? RubyFixnum.one(getRuntime()) : RubyFixnum.newFixnum(getRuntime(), regs.numRegs);
    }

    /** match_begin
     *
     */
    public  function begin(index:IRubyObject):IRubyObject{
        var i: int =  RubyNumeric.num2int(index);

        if (regs == null) {
            if (i != 0) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (begin < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), begin);
        } else {
            if (i < 0 || regs.numRegs <= i) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (regs.beg[i] < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), regs.beg[i]);
        }
    }

    /** match_end
     *
     */
    public  function end(index:IRubyObject):IRubyObject{
        var i: int =  RubyNumeric.num2int(index);

        if (regs == null) {
            if (i != 0) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (end < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), end);
        } else {
            if (i < 0 || regs.numRegs <= i) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (regs.end[i] < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), regs.end[i]);
        }
    }

    /** match_offset
     *
     */
    public  function offset(index:IRubyObject):IRubyObject{
        var i: int =  RubyNumeric.num2int(index);
        var runtime: Ruby =  getRuntime();

        if (regs == null) {
            if (i != 0) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (begin < 0) return runtime.newArray(runtime.getNil(), runtime.getNil());
            return runtime.newArray(RubyFixnum.newFixnum(runtime, begin),RubyFixnum.newFixnum(runtime, end));            
        } else {
            if (i < 0 || regs.numRegs <= i) throw runtime.newIndexError("index " + i + " out of matches");
            if (regs.beg[i] < 0) return runtime.newArray(runtime.getNil(), runtime.getNil());
            return runtime.newArray(RubyFixnum.newFixnum(runtime, regs.beg[i]),RubyFixnum.newFixnum(runtime, regs.end[i]));
        }
    }

    /** match_pre_match
     *
     */
    public  function pre_match():IRubyObject{
        var ss:RubyString;
        
        if (regs == null) {
            if(begin == -1) return getRuntime().getNil();
            ss = str.makeShared(0, begin);
        } else {
            if(regs.beg[0] == -1) return getRuntime().getNil();
            ss = str.makeShared(0, regs.beg[0]);
        }
        
        if (isTaint()) ss.setTaint(true);
        return ss;
    }

    /** match_post_match
     *
     */
    public  function post_match():IRubyObject{
        var ss:RubyString;
        
        if (regs == null) {
            if (begin == -1) return getRuntime().getNil();
            ss = str.makeShared(end, str.getByteList().length() - end);
        } else {
            if (regs.beg[0] == -1) return getRuntime().getNil();
            ss = str.makeShared(regs.end[0], str.getByteList().length() - regs.end[0]);
        }
        
        if(isTaint()) ss.setTaint(true);
        return ss;
    }

    /** match_to_s
     *
     */
    public override function to_s():IRubyObject{
        var ss: IRubyObject =  RubyRegexp.last_match(this);
        if (ss.isNil()) ss = RubyString.newEmptyString(getRuntime());
        if (isTaint()) ss.setTaint(true);
        return ss;
    }

    /** match_string
     *
     */
    public  function string():IRubyObject{
        return str; //str is frozen
    }

    public override function initialize_copy(original:IRubyObject):IRubyObject{
        if (this == original) return this;
        
        if (!(getMetaClass() == original.getMetaClass())){ // MRI also does a pointer comparison here
            throw getRuntime().newTypeError("wrong argument class");
        }

        var origMatchData: RubyMatchData = RubyMatchData(original);
        str = origMatchData.str;
        regs = origMatchData.regs;

        return this;
    }
}
}